package com.luych.toolbox.common.xml;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class XmlNodeReader {

    private final List<XmlNode> nodes;

    public XmlNodeReader(List<XmlNode> nodes) {
        this.nodes = nodes;
    }

    public boolean exists() {
        return this.nodes().size() > 0;
    }

    public void exists(Consumer<Boolean> consumer) {
        consumer.accept(this.exists());
    }

    public List<XmlNode> nodes() {
        return Optional.ofNullable(nodes).orElse(new ArrayList<>());
    }

    public void nodes(Consumer<List<XmlNode>> consumer) {
        consumer.accept(this.nodes());
    }

    public List<String> stringValues() {
        return this.nodes.stream().map(XmlNode::getValue).collect(Collectors.toList());
    }

    public void stringValues(Consumer<List<String>> consumer) {
        consumer.accept(this.stringValues());
    }

    public String stringValuesJoin(String separator) {
        return String.join(separator, this.stringValues());
    }

    public void stringValuesJoin(String separator, Consumer<String> consumer) {
        consumer.accept(this.stringValuesJoin(separator));;
    }

    public List<Boolean> booleanValues() {
        return this.stringValues().stream().map(Boolean::parseBoolean).collect(Collectors.toList());
    }

    public void booleanValues(Consumer<List<Boolean>> consumer) {
        consumer.accept(this.booleanValues());
    }

    public List<Integer> integerValues() {
        return this.stringValues().stream().map(Integer::parseInt).collect(Collectors.toList());
    }

    public void integerValues(Consumer<List<Integer>> consumer) {
        consumer.accept(this.integerValues());
    }

    public List<Long> longValues() {
        return this.stringValues().stream().map(Long::parseLong).collect(Collectors.toList());
    }

    public void longValues(Consumer<List<Long>> consumer) {
        consumer.accept(this.longValues());
    }

    public List<Float> floatValues() {
        return this.stringValues().stream().map(Float::parseFloat).collect(Collectors.toList());
    }

    public void floatValues(Consumer<List<Float>> consumer) {
        consumer.accept(this.floatValues());
    }

    public List<Double> doubleValues() {
        return this.stringValues().stream().map(Double::parseDouble).collect(Collectors.toList());
    }

    public void doubleValues(Consumer<List<Double>> consumer) {
        consumer.accept(this.doubleValues());
    }

    public List<Date> dateValues(String format) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
        return this.stringValues().stream().map(string -> {
            try {
                return simpleDateFormat.parse(string);
            } catch (ParseException e) {
                throw new IllegalStateException(e);
            }
        }).collect(Collectors.toList());
    }

    public void dateValues(String format, Consumer<List<Date>> consumer) {
        consumer.accept(this.dateValues(format));
    }

    public List<Map<String, String>> attributes() {
        return this.nodes().stream().map(XmlNode::getAttrs).collect(Collectors.toList());
    }

    public List<String> stringAttributes(String key) {
        return this.attributes().stream().map(map -> map.get(key)).collect(Collectors.toList());
    }

    public void stringAttributes(String key, Consumer<List<String>> consumer) {
        consumer.accept(this.stringAttributes(key));
    }

    public List<Boolean> booleanAttributes(String key) {
        return this.stringAttributes(key).stream().map(Boolean::parseBoolean).collect(Collectors.toList());
    }

    public void booleanAttributes(String key, Consumer<List<Boolean>> consumer) {
        consumer.accept(this.booleanAttributes(key));
    }

    public List<Integer> integerAttributes(String key) {
        return this.stringAttributes(key).stream().map(Integer::parseInt).collect(Collectors.toList());
    }

    public void integerAttributes(String key, Consumer<List<Boolean>> consumer) {
        consumer.accept(this.booleanAttributes(key));
    }

    public List<Long> longAttributes(String key) {
        return this.stringAttributes(key).stream().map(Long::parseLong).collect(Collectors.toList());
    }

    public void longAttributes(String key, Consumer<List<Long>> consumer) {
        consumer.accept(this.longAttributes(key));
    }

    public List<Float> floatAttributes(String key) {
        return this.stringAttributes(key).stream().map(Float::parseFloat).collect(Collectors.toList());
    }

    public void floatAttributes(String key, Consumer<List<Float>> consumer) {
        consumer.accept(this.floatAttributes(key));
    }

    public List<Double> doubleAttributes(String key) {
        return this.stringAttributes(key).stream().map(Double::parseDouble).collect(Collectors.toList());
    }

    public void doubleAttributes(String key, Consumer<List<Double>> consumer) {
        consumer.accept(this.doubleAttributes(key));
    }

    public List<Date> dateAttributes(String key, String format) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
        return this.stringAttributes(key).stream().map(string -> {
            try {
                return simpleDateFormat.parse(string);
            } catch (ParseException e) {
                throw new IllegalStateException(e);
            }
        }).collect(Collectors.toList());
    }

    public void dateAttributes(String key, String format, Consumer<List<Date>> consumer) {
        consumer.accept(this.dateAttributes(key, format));
    }

    public Optional<XmlNode> node() {
        return Optional.ofNullable(nodes).map(array -> array.stream().findFirst().orElse(null));
    }

    public void node(Consumer<XmlNode> consumer) {
        this.node().ifPresent(consumer);
    }

    public Optional<String> stringValue() {
        return this.node().map(XmlNode::getValue);
    }

    public void stringValue(Consumer<String> consumer) {
        this.stringValue().ifPresent(consumer);
    }

    public Optional<Boolean> booleanValue() {
        return this.stringValue().map(Boolean::parseBoolean);
    }

    public void booleanValue(Consumer<Boolean> consumer) {
        this.booleanValue().ifPresent(consumer);
    }

    public Optional<Integer> integerValue() {
        return this.stringValue().map(Integer::parseInt);
    }

    public void integerValue(Consumer<Integer> consumer) {
        this.integerValue().ifPresent(consumer);
    }

    public Optional<Long> longValue() {
        return this.stringValue().map(Long::parseLong);
    }

    public void longValue(Consumer<Long> consumer) {
        this.longValue().ifPresent(consumer);
    }

    public Optional<Float> floatValue() {
        return this.stringValue().map(Float::parseFloat);
    }

    public void floatValue(Consumer<Float> consumer) {
        this.floatValue().ifPresent(consumer);
    }

    public Optional<Double> doubleValue() {
        return this.stringValue().map(Double::parseDouble);
    }

    public void doubleValue(Consumer<Double> consumer) {
        this.doubleValue().ifPresent(consumer);
    }

    public Optional<Date> dateValue(String format) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
        return this.stringValue().map(string -> {
            try {
                return simpleDateFormat.parse(string);
            } catch (ParseException e) {
                throw new IllegalStateException(e);
            }
        });
    }

    public void dateValue(String format, Consumer<Date> consumer) {
        this.dateValue(format).ifPresent(consumer);
    }

    public Map<String, String> attribute() {
        return this.node().map(XmlNode::getAttrs).orElse(new HashMap<>());
    }

    public Optional<String> stringAttribute(String key) {
        return Optional.ofNullable(this.attribute().get(key));
    }

    public void stringAttribute(String key, Consumer<String> consumer) {
        this.stringAttribute(key).ifPresent(consumer);
    }

    public Optional<Boolean> booleanAttribute(String key) {
        return this.stringAttribute(key).map(Boolean::parseBoolean);
    }

    public void booleanAttribute(String key, Consumer<Boolean> consumer) {
        this.booleanAttribute(key).ifPresent(consumer);
    }

    public Optional<Integer> integerAttribute(String key) {
        return this.stringAttribute(key).map(Integer::parseInt);
    }

    public void integerAttribute(String key, Consumer<Integer> consumer) {
        this.integerAttribute(key).ifPresent(consumer);
    }

    public Optional<Long> longAttribute(String key) {
        return this.stringAttribute(key).map(Long::parseLong);
    }

    public void longAttribute(String key, Consumer<Long> consumer) {
        this.longAttribute(key).ifPresent(consumer);
    }

    public Optional<Float> floatAttribute(String key) {
        return this.stringAttribute(key).map(Float::parseFloat);
    }

    public void floatAttribute(String key, Consumer<Float> consumer) {
        this.floatAttribute(key).ifPresent(consumer);
    }

    public Optional<Double> doubleAttribute(String key) {
        return this.stringAttribute(key).map(Double::parseDouble);
    }

    public void doubleAttribute(String key, Consumer<Double> consumer) {
        this.doubleAttribute(key).ifPresent(consumer);
    }

    public Optional<Date> dateAttribute(String key, String format) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
        return this.stringAttribute(key).map(string -> {
            try {
                return simpleDateFormat.parse(string);
            } catch (ParseException e) {
                throw new IllegalStateException(e);
            }
        });
    }

    public void dateAttribute(String key, String format, Consumer<Date> consumer) {
        this.dateAttribute(key, format).ifPresent(consumer);
    }

    public XmlNodeReader children(String... names) {
        List<XmlNode> children = new ArrayList<>();
        Optional.ofNullable(nodes).orElse(new ArrayList<>()).stream()
                .map(XmlNode::getChildren)
                .map(array -> array.stream()
                        .filter(item -> names == null || names.length == 0 || Arrays.asList(names).contains(item.getName()))
                        .collect(Collectors.toList()))
                .forEach(children::addAll);
        return new XmlNodeReader(children);
    }
}
